/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
 
package com.cburch.logisim.std.plexers;

import java.awt.Color;
import java.awt.Graphics;

import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Attributes;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.util.Strings;
import com.cburch.logisim.UItools.GraphicsUtil;

public class BitSelector extends InstanceFactory {
    public static final Attribute GROUP_ATTR
        = Attributes.forBitWidth("group", Strings.getter("bitSelectorGroupAttr", Strings.stdSource));

    public BitSelector() {
        super("BitSelector", Strings.getter("bitSelectorComponent", Strings.stdSource));
        setAttributes(new Attribute[] {
                StdAttr.FACING, StdAttr.WIDTH, GROUP_ATTR
            }, new Object[] {
                Direction.EAST, BitWidth.create(8), BitWidth.ONE
            });
        setIconName("bitSelector.gif");
        setFacingAttribute(StdAttr.FACING);
    }
    
    public Bounds getOffsetBounds(AttributeSet attrs) {
        Direction facing = (Direction) attrs.getValue(StdAttr.FACING);
        Bounds base = Bounds.create(-30, -15, 30, 30);
        return base.rotate(Direction.EAST, facing, 0, 0);
    }
    
    protected void configureNewInstance(Instance instance) {
        instance.addAttributeListener();
        updatePorts(instance);
    }
    
    protected void instanceAttributeChanged(Instance instance, Attribute attr) {
        if(attr == StdAttr.FACING) {
            instance.recomputeBounds();
            updatePorts(instance);
        } else if(attr == StdAttr.WIDTH || attr == GROUP_ATTR) {
            updatePorts(instance);
        }
    }

    private void updatePorts(Instance instance) {
        Direction facing = (Direction) instance.getAttributeValue(StdAttr.FACING);
        BitWidth data = (BitWidth) instance.getAttributeValue(StdAttr.WIDTH);
        BitWidth group = (BitWidth) instance.getAttributeValue(GROUP_ATTR);
        int groups = (data.getWidth() + group.getWidth() - 1) / group.getWidth() - 1;
        int selectBits = 1;
        if(groups > 0) {
            while(groups != 1) { groups >>= 1; selectBits++; }
        }
        BitWidth select = BitWidth.create(selectBits);

        Location inPt;
        Location selPt;
        if(facing == Direction.WEST) {
            inPt  = Location.create(30, 0);
            selPt = Location.create(10, 10);
        } else if(facing == Direction.NORTH) {
            inPt  = Location.create(  0, 30);
            selPt = Location.create(-10, 10);
        } else if(facing == Direction.SOUTH) {
            inPt  = Location.create(  0, -30);
            selPt = Location.create(-10, -10);
        } else {
            inPt  = Location.create(-30, 0);
            selPt = Location.create(-10, 10);
        }
        
        Port[] ps = new Port[3];
        ps[0] = new Port(0, 0, Port.OUTPUT, group.getWidth());
        ps[1] = new Port(inPt.getX(), inPt.getY(), Port.INPUT, data.getWidth());
        ps[2] = new Port(selPt.getX(), selPt.getY(), Port.INPUT, select.getWidth());
        ps[0].setToolTip(Strings.getter("bitSelectorOutputTip", Strings.stdSource));
        ps[1].setToolTip(Strings.getter("bitSelectorDataTip", Strings.stdSource));
        ps[2].setToolTip(Strings.getter("bitSelectorSelectTip", Strings.stdSource));
        instance.setPorts(ps);
    }

    public void propagate(InstanceState state) {
        Value data = state.getPort(1);
        Value select = state.getPort(2);
        BitWidth groupBits = (BitWidth) state.getAttributeValue(GROUP_ATTR);
        Value group;
        if(!select.isFullyDefined()) {
            group = Value.createUnknown(groupBits);
        } else {
            int shift = select.toIntValue() * groupBits.getWidth();
            if(shift >= data.getWidth()) {
                group = Value.createKnown(groupBits, 0);
            } else if(groupBits.getWidth() == 1) {
                group = data.get(shift);
            } else {
                Value[] bits = new Value[groupBits.getWidth()];
                for(int i = 0; i < bits.length; i++) {
                    if(shift + i >= data.getWidth()) {
                        bits[i] = Value.FALSE;
                    } else {
                        bits[i] = data.get(shift + i);
                    }
                }
                group = Value.create(bits);
            }
        }
        state.setPort(0, group, Plexers.DELAY);
    }

    public void paintGhost(InstancePainter painter) {
        Plexers.drawTrapezoid(painter.getGraphics(), painter.getBounds(),
                (Direction) painter.getAttributeValue(StdAttr.FACING), 9);
    }

    public void paintInstance(InstancePainter painter) {
        Graphics g = painter.getGraphics();
        Direction facing = (Direction) painter.getAttributeValue(StdAttr.FACING);

        Plexers.drawTrapezoid(g, painter.getBounds(), facing, 9);
        Bounds bds = painter.getBounds();
        g.setColor(Color.BLACK);
        GraphicsUtil.drawCenteredText(g, "Sel",
                bds.getX() + bds.getWidth() / 2,
                bds.getY() + bds.getHeight() / 2);
        painter.drawPorts();
    }
}
